﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.ReportGenerator.BL;
using Sustainalytics.Utils;

namespace Sustainalytics.CompanyReportSyncStep.HistoricalData
{
    public class HistoricalData
    {

        public CompanyHistoryData CompanyHistory { get; private set; }

     
        public CompanyHistoricalPerformance CompanyPerformanceData { get; private set; }

        public CompanyHistoricalDataIndicators IndicatorsData { get; private set; }


        public void SetHistoricalData(CompanyHistoryData companyHistoryData)
        {
            if (companyHistoryData == null)
            {
                throw new ArgumentNullException("companyHistoryData", "Invalid argument");
            }

            CompanyHistory = companyHistoryData;
            CompanyPerformanceData = new CompanyHistoricalPerformance
            {
                Id = companyHistoryData.Id,
                Last36Months = new List<CompanyHistoryData> { companyHistoryData }
            };
        }

        public void SetIndicatorsData(DateTime processedDate, Guid companyId, string capitalIQID, IEnumerable<IndicatorData> indicatorsData)
        {
            if (indicatorsData == null)
            {
                throw new ArgumentNullException("indicatorsData", "Invalid argument");
            }
            IndicatorsData = new CompanyHistoricalDataIndicators
            {
                ProcessedDate = processedDate,
                CompanyId = companyId,
                CapitalIQID = capitalIQID,
                Indicators = indicatorsData.ToList()
            };
        }
    }

    public class HistoricalDataManager : Disposable, IHistoricalDataManager
    {
        private readonly ESGRatingsAdapter _esgRatingsAdapter;
        private readonly IRepositoryCollection<CompanyHistoryData> _historicalDataRepository;
        private readonly IRepositoryCollection<CompanyHistoricalDataIndicators> _historicalIndicatorsRepository;
        private readonly IRepositoryCollection<CompanyHistoricalPerformance> _historicalPerformanceRepository;

        private HistoricalData _dataToUpdate;
        private HistoricalDataComputationContext _computationContext;

        private Dictionary<Type, bool> _updateMaskDictionary;

        public HistoricalDataManager(IRepositoryCollection<CompanyHistoryData> historicalDataRepository,
            IRepositoryCollection<CompanyHistoricalPerformance> historicalPerformanceRepository,
            IRepositoryCollection<CompanyHistoricalDataIndicators> historicalIndicatorsRepository,
            ESGRatingsAdapter esgRatingsAdapter)
        {
            if (historicalDataRepository == null)
            {
                throw new ArgumentNullException("historicalDataRepository", "Invalid argument");
            }

            if (historicalPerformanceRepository == null)
            {
                throw new ArgumentNullException("historicalPerformanceRepository", "Invalid argument");
            }

            if (historicalIndicatorsRepository == null)
            {
                throw new ArgumentNullException("historicalIndicatorsRepository", "Invalid argument");
            }

            _historicalDataRepository = historicalDataRepository;
            _historicalPerformanceRepository = historicalPerformanceRepository;
            _historicalIndicatorsRepository = historicalIndicatorsRepository;
            _esgRatingsAdapter = esgRatingsAdapter;
        }

        public void SetIndicatiors(IEnumerable<RpIndicatorCommon> indicators)
        {
            var rpIndicatorExtendeds = indicators as RPIndicatorExtended[] ?? indicators.ToArray();

            if (rpIndicatorExtendeds.IsNullOrEmpty())
                throw new ArgumentNullException("indicators");
            if (_computationContext == null)
                throw new Exception("Invalid historical computation context");

            var date = GetFirstOfMonth(_computationContext.ProcessingDate);

            if (_dataToUpdate==null)
                _dataToUpdate=new HistoricalData();

            _dataToUpdate.SetIndicatorsData(date, _computationContext.CompanyId, _computationContext.CapitalIQID
                , rpIndicatorExtendeds.Select(x => new IndicatorData
                {
                    IndicatorNumber = x.Number,
                    RawScore = x.SelectedAnswer.Score,
                    Weight = x.Weight
                }));
        }

        public void SetHistoricalCompanyData()
        {

            if (_computationContext == null)
                throw new Exception("Invalid historical computation context");
            if (_dataToUpdate == null)
                _dataToUpdate = new HistoricalData();

           
            var date = GetFirstOfMonth(_computationContext.ProcessingDate);
            _dataToUpdate.SetHistoricalData(ComputeHistoryPerformancePerMonth(date, _computationContext.CompanyId));
        }

        public void SetComputationContext(Guid companyId, string capitalIQID, DateTime? date = null)
        {
            if (date == null)
            {
                date = DateTime.Today.ToUniversalTime();
            }
           _computationContext=new HistoricalDataComputationContext(companyId,date.Value,capitalIQID);
        }

        public bool SaveHistoricalData()
        {

            if (_updateMaskDictionary==null)
                throw new Exception("Invalid historical data");

            if(_updateMaskDictionary[typeof(CompanyHistoricalDataIndicators)] && _dataToUpdate.IndicatorsData==null)
                throw new Exception("Invalid Indicator list");


            if (_updateMaskDictionary[typeof(CompanyHistoricalPerformance)] && _dataToUpdate.CompanyPerformanceData==null)
                throw new Exception("Invalid historical performance data");


            var historicalDataPersistence = new HistoricalDataPersistance(_historicalIndicatorsRepository,
                _historicalPerformanceRepository, _historicalDataRepository);
            return historicalDataPersistence.WriteDataAsync(_dataToUpdate);

        }

        public bool ShouldUpdate()
        {

            _updateMaskDictionary = ShoulUpdate(GetFirstOfMonth(_computationContext.ProcessingDate), _computationContext.CompanyId);

            return (_computationContext.ProcessingDate == GetFirstOfMonth(_computationContext.ProcessingDate) && (_updateMaskDictionary.Values.Any(x => x))) ||  (_updateMaskDictionary.Values.Any(x => x));
        }

        public CompanyHistoryData GetCompanyHistoryData()
        {
            return _dataToUpdate.CompanyHistory;
        }

        private CompanyHistoryData ComputeHistoryPerformancePerMonth(DateTime date, Guid companyId)
        {
            var esgData = _esgRatingsAdapter.GetEsgData(companyId);
            var bestInClassData = _esgRatingsAdapter.GetBestInClass(companyId);
            var esgAvg = _esgRatingsAdapter.GetESGAvg(companyId);

            var todayRecord = new CompanyHistoryData
            {
                Date = date,
                OverallScore = esgData.Score,
                GovernanceScore = esgData.GovernanceScore,
                SocialScore = esgData.SocialScore,
                EnvironmentScore = esgData.EnvironmentScore,

                OverallAverage = esgAvg.OverallAvg,
                GovernanceAverage = esgAvg.GAvg,
                SocialAverage = esgAvg.SAvg,
                EnvironmentAverage = esgAvg.EAvg,

                OverallBestInClass = bestInClassData.OverallBestInClass,
                GovernanceBestInClass = bestInClassData.GBestInClass,
                SocialBestInClass = bestInClassData.SBestInClass,
                EnvironmentBestInClass = bestInClassData.EBestInClass,
            };
            return todayRecord;
        }

        private DateTime GetFirstOfMonth(DateTime refDate)
        {
            return new DateTime(refDate.Year, refDate.Month, 1, 0, 0, 0, DateTimeKind.Utc);
        }

        private bool ShouldUpdateHistory(DateTime date, Guid companyId)
        {
            date = GetFirstOfMonth(date);
            var histData = _historicalDataRepository.ReadAllWhere(x => x.Id == companyId && x.Date == date);
            return histData.IsNullOrEmpty();
        }

        private Dictionary<Type, bool> ShoulUpdate(DateTime date, Guid companyId)
        {
            var functionsDictionary = new Dictionary<Type, Func<DateTime, Guid, bool>>
            {
                {typeof (CompanyHistoricalDataIndicators), ShoulUpdateIndicators},
                {typeof (CompanyHistoryData), ShouldUpdateHistory},
                {typeof (CompanyHistoricalPerformance), ShoulUpdatePerformance}
            };

            return functionsDictionary.ToDictionary(k => k.Key, v => v.Value(date, companyId));
        }

        private bool ShoulUpdateIndicators(DateTime date, Guid companyId)
        {
            date = GetFirstOfMonth(date);
            var histIndicators =
                _historicalIndicatorsRepository.ReadAllWhere(x => x.CompanyId == companyId && x.ProcessedDate == date);
            return histIndicators.IsNullOrEmpty();
        }

        private bool ShoulUpdatePerformance(DateTime date, Guid companyId)
        {
            var history = _historicalPerformanceRepository.ReadAllWhere(x => x.Id == companyId).FirstOrDefault();
            if (history == null)
            {
                return true;
            }
            date = GetFirstOfMonth(date);
            return !history.Last36Months.All(d => d.Date == (date));
        }
    }

    public class HistoricalDataPersistance
    {
        private readonly IRepositoryCollection<CompanyHistoryData> _historicalDataRepository;
        private readonly IRepositoryCollection<CompanyHistoricalDataIndicators> _historicalIndicatorsRepository;
        private readonly IRepositoryCollection<CompanyHistoricalPerformance> _historicalPerformanceRepository;

        public HistoricalDataPersistance(
            IRepositoryCollection<CompanyHistoricalDataIndicators> historicalIndicatorsRepository,
            IRepositoryCollection<CompanyHistoricalPerformance> historicalPerformanceRepository,
            IRepositoryCollection<CompanyHistoryData> historicalDataRepository)
        {
            _historicalIndicatorsRepository = historicalIndicatorsRepository;
            _historicalPerformanceRepository = historicalPerformanceRepository;
            _historicalDataRepository = historicalDataRepository;
        }

        public bool WriteDataAsync(HistoricalData data)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            var listOfTasks = new List<Task<bool>>();
            if (data.CompanyHistory != null)
            {
                listOfTasks.Add(UpdateHistoryAsync(data.CompanyHistory));
            }
            if (data.IndicatorsData != null)
            {
                listOfTasks.Add(UpdateIndicatorsAsync(data.CompanyHistory));
            }
            if (data.CompanyPerformanceData != null)
            {
                listOfTasks.Add(UpdateCompanyPerformanceHistoryAsync(data.CompanyPerformanceData));
            }

            var resultTask = listOfTasks.All(x => x.Status == TaskStatus.RanToCompletion)
                ? Task.FromResult(GetWrittingResult(listOfTasks.Select(x => x.Result)))
                : GetResultAsyncInternal(listOfTasks);

            return resultTask.Result;
        }

        private async Task<bool> GetResultAsyncInternal(IList<Task<bool>> listOfTasks)
        {
            await Task.WhenAll(listOfTasks).ConfigureAwait(false);
            return GetWrittingResult(listOfTasks.Select(x => x.Result));
        }

        private bool GetWrittingResult(IEnumerable<bool> resultList)
        {
            return resultList.All(x => x);
        }

        private Task<bool> UpdateCompanyPerformanceHistoryAsync(IEntity data)
        {
            var dataToSave = data as CompanyHistoricalPerformance;
            if (data == null || dataToSave == null)
            {
                return Task.FromResult(false);
            }

            if (dataToSave.Last36Months.IsNullOrEmpty())
            {
                return Task.FromResult(false);
            }

            return Task.Run(() => _historicalPerformanceRepository.CreateOrUpdate(dataToSave));
        }

        private Task<bool> UpdateHistoryAsync(IEntity data)
        {
            var dataToSave = data as CompanyHistoryData;
            if (data == null)
            {
                return Task.FromResult(false);
            }
            return Task.Run(() => _historicalDataRepository.Create(dataToSave));
        }

        private Task<bool> UpdateIndicatorsAsync(IEntity data)
        {
            var dataToSave = data as CompanyHistoricalDataIndicators;
            if (data == null || dataToSave == null)
            {
                return Task.FromResult(false);
            }

            return Task.Run(() => _historicalIndicatorsRepository.Create(dataToSave));
        }
    }
}